/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package commandhandlers;

import input.IRCCommand;
import interfaces.CommandHandler;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.NumberFormat;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import main.CommonMethods;
import main.Constants;
import main.Main;
import main.UserCentral;

import org.apache.log4j.Logger;
import org.jibble.pircbot.Colors;

import output.IRCOutput;
import tools.InputType;
import tools.UtopiaTime;
import botobjects.InstantDamageSpellOp;
import botobjects.SpellOp;
import database.DBConnectionFactory;

/**
 * This class handles commands related to spells and ops
 * 
 * 
 */
public class SpellsOpsHandler extends CommandHandler {
   /**
    * Helper class for the dynamic calling of various spell/op commands
    * 
    * 
    */
   private static class SpellOpCommand {
	  private final String name;
	  private final boolean wasAccess;
	  private final int commandType;

	  public SpellOpCommand(final String name, final boolean wasAccess, final int commandType) {
		 this.name = name;
		 this.wasAccess = wasAccess;
		 this.commandType = commandType;
	  }

	  public int getCommandType() {
		 return commandType;
	  }

	  public String getName() {
		 return name;
	  }

	  public boolean wasAccess() {
		 return wasAccess;
	  }
   }

   /**
    * Helper class for spell/ops
    * 
    * 
    */
   private static class SpellOpPair {
	  public int result;
	  public int times;

	  public SpellOpPair(final int result, final int times) {
		 this.result = result;
		 this.times = times;
	  }
   }

   private static SpellsOpsHandler INSTANCE = null;

   public static final SpellsOpsHandler getInstance() {
	  if (INSTANCE == null) {
		 INSTANCE = new SpellsOpsHandler();
	  }
	  return INSTANCE;
   }

   /**
    * Returns the current target for spells/ops for the specified user
    * 
    * @param user
    * @return
    */
   public static String getSpellOpTarget(final String user) {
	  String out = "";
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM spelloptarget WHERE nick = ?");
		 ps.setString(1, user);
		 rs = ps.executeQuery();
		 if (rs.next()) {
			out = rs.getString("target");
		 }
	  } catch (SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (Exception e) {
		 }
		 try {
			ps.close();
		 } catch (Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Prints the spells/ops ending in the specified hour
    * 
    * @param utotime
    * @return
    */
   public static List<String> getSpellsEndingThisHour(final String utotime) {
	  List<String> out = new LinkedList<String>();
	  Statement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().createStatement();
		 String query = "SELECT * FROM spellop,kdprovince WHERE spellop.province = kdprovince.province";
		 rs = ps.executeQuery(query);

		 while (rs.next()) {
			UtopiaTime ut = new UtopiaTime(rs.getTimestamp("ends").getTime());
			if (ut.formattedUT().equals(utotime)) {
			   out.add(Colors.BLUE + rs.getString("mainnick") + "'s " + Colors.TEAL + rs.getString("name") + Colors.OLIVE
					 + " ends this hour");
			}
		 }

		 try {
			ps.close();
		 } catch (final Exception e) {
		 }

		 ps = DBConnectionFactory.getConnectionHandler().getConnection().createStatement();
		 query = "SELECT * FROM spellop WHERE NOT EXISTS(SELECT province FROM kdprovince WHERE "
			   + "spellop.province = kdprovince.province)";
		 rs = ps.executeQuery(query);

		 while (rs.next()) {
			UtopiaTime ut = new UtopiaTime(rs.getTimestamp("ends").getTime());
			if (ut.formattedUT().equals(utotime)) {
			   out.add(Colors.BLUE + rs.getString("province") + "'s " + Colors.OLIVE + rs.getString("spellop.name")
					 + " ends this hour");
			}
		 }
		 removeOldSpells();
	  } catch (SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (Exception e) {
		 }
		 try {
			ps.close();
		 } catch (Exception e) {
		 }
	  }
	  return out;
   }

   public static final SpellsOpsHandler newInstance() {
	  INSTANCE = null;
	  return getInstance();
   }

   /**
    * Clears the list for the specified type of spell/op
    * 
    * @param deleteType
    * @param arg
    * @return
    */
   public static boolean removeInstantDamageSpellOp(final int deleteType, final String... arg) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  try {
		 if (deleteType == Constants.IDSPELLOP_DELETE_SPELLOP && arg.length == 1) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("DELETE FROM idspellop WHERE spellop = ?");
			ps.setString(1, arg[0]);
		 } else if (deleteType == Constants.IDSPELLOP_DELETE_FOR_ALL_BUT_KD && arg.length == 1) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("DELETE FROM idspellop WHERE NOT EXISTS(SELECT * FROM province WHERE kdloc = ?)");
			ps.setString(1, arg[0]);
		 } else if (deleteType == Constants.IDSPELLOP_DELETE_SPELLOP_FOR_PROV && arg.length == 2) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("DELETE FROM idspellop WHERE spellop = ? AND province = ?");
			ps.setString(1, arg[0]);
			ps.setString(2, arg[1]);
		 } else if (deleteType == Constants.IDSPELLOP_DELETE_SPELLOP_FOR_PLAYER && arg.length == 2) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("DELETE FROM idspellop WHERE spellop = ? AND nick = ?");
			ps.setString(1, arg[0]);
			ps.setString(2, arg[1]);
		 } else if (deleteType == Constants.IDSPELLOP_DELETE_SPELLOP_FOR_KD && arg.length == 2) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("DELETE FROM idspellop WHERE spellop = ? AND EXISTS(SELECT * FROM province WHERE kdloc = ?)");
			ps.setString(1, arg[0]);
			ps.setString(2, arg[1]);
		 } else if (deleteType == Constants.IDSPELLOP_DELETE_ALL_SPELLOPS_FOR_KD && arg.length == 1) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("DELETE FROM idspellop WHERE  EXISTS(SELECT * FROM province WHERE kdloc = ?)");
			ps.setString(1, arg[0]);
		 } else if (deleteType == Constants.IDSPELLOP_DELETE_ALL_SPELLOPS) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("DELETE FROM idspellop");
		 } else if (deleteType == Constants.IDSPELLOP_DELETE_SPELLOP_FOR_ALL_BUT_KD && arg.length == 2) {
			ps = DBConnectionFactory
				  .getConnectionHandler()
				  .getConnection()
				  .prepareStatement(
						"DELETE FROM idspellop WHERE spellop = ? AND NOT EXISTS(SELECT * FROM province WHERE kdloc = ?)");
			ps.setString(1, arg[0]);
			ps.setString(2, arg[1]);
		 }

		 if (ps != null && ps.executeUpdate() > 0) {
			out = true;
		 }
	  } catch (SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Removes spells that have expired
    */
   public static void removeOldSpells() {
	  Statement stmt = null;
	  ResultSet rs = null;
	  try {
		 stmt = DBConnectionFactory.getConnectionHandler().getConnection()
			   .createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
		 String query = "SELECT * FROM spellop";
		 rs = stmt.executeQuery(query);

		 while (rs.next()) {
			if (rs.getTimestamp("ends").getTime() <= GregorianCalendar.getInstance().getTimeInMillis()) {
			   rs.deleteRow();
			}
		 }
	  } catch (SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (Exception e) {
		 }
		 try {
			stmt.close();
		 } catch (Exception e) {
		 }
	  }
   }

   /**
    * Sets a target for pasting spells and ops that don't include the target in the ingame result message
    * 
    * @param user
    * @param target
    * @return
    */
   public static boolean setSpellOpTarget(final String user, final String target) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("DELETE FROM spelloptarget WHERE nick = ?");
		 ps.setString(1, user);
		 ps.executeUpdate();

		 try {
			ps.close();
		 } catch (Exception e) {
		 }

		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("INSERT INTO spelloptarget VALUES(?,?)");
		 ps.setString(1, user);
		 ps.setString(2, target);
		 if (ps.executeUpdate() == 1) {
			out = true;
		 }
	  } catch (SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (Exception e) {
		 }
	  }
	  return out;
   }

   private SpellsOpsHandler() {
   }

   /**
    * Returns a SpellOpCommand for the specified command. The Spell/Op commands are not hard coded but can be added to the
    * database along with regex for different spells and ops, so they need to be handled differently
    * 
    * @param command
    * @return
    */
   private SpellOpCommand getSpellOpCommand(final String command) {
	  SpellOpCommand out = null;
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM " + "textinput WHERE accesscommand = ? OR resetcommand = ?");
		 ps.setString(1, command);
		 ps.setString(2, command);
		 rs = ps.executeQuery();

		 if (rs.next() && (!"".equals(rs.getString("accesscommand")) || !"".equals(rs.getString("resetcommand")))) {
			out = new SpellOpCommand(rs.getString("name"), rs.getString("accesscommand").equals(command), rs.getInt("inputtype"));
		 }
	  } catch (SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (Exception e) {
		 }
		 try {
			ps.close();
		 } catch (Exception e) {
		 }
	  }
	  return out;
   }

   @Override
   public void handle(final IRCCommand command, final IRCOutput out) {
	  SpellOpCommand soc = getSpellOpCommand(command.get(0));
	  if (soc != null) {
		 if (soc.wasAccess()) {
			if (soc.getCommandType() == 1) {
			   // nothing is done for self spells atm
			} else if (soc.getCommandType() == 2 || soc.getCommandType() == 3) {
			   if (command.size() == 1) {
				  out.addOutput(listSpellOp(Constants.SPELLOP_GET_FOR_SPELLOP_FOR_ALL, soc.getName()));
			   } else {
				  if (command.validate(1, InputType.KDLOC)) {
					 out.addOutput(listSpellOp(Constants.SPELLOP_GET_FOR_SPELLOP_FOR_KD, soc.getName(), command.get(1)));
				  } else if (command.size() > 2 && command.isInt(1)) {
					 String prov = command.getPartialMessage(2, command.size());
					 if (prov.matches(".*\\([0-9]{1,2}(:)[0-9]{1,2}\\)")) {
						String provname = prov.substring(0, prov.indexOf('(')).trim();
						provname = CommonMethods.provinceExists(provname);
						String kdloc = prov.substring(prov.indexOf('('));
						Long endtime = GregorianCalendar.getInstance().getTimeInMillis()
							  + (long) ((Double.parseDouble(command.get(1)) + 1) * Main.getBotSettings().getTickLength() * 60 * 1000);
						endtime -= endtime % (Main.getBotSettings().getTickLength() * 60L * 1000);
						if (!"".equals(provname)) {
						   SpellOp temp = new SpellOp(provname, kdloc, soc.getName(), endtime, command.getSender());
						   if (temp.saveToDB()) {
							  out.addOutput(temp.printSave());
						   }
						}
					 }
				  }
			   }
			} else if (soc.getCommandType() == 4 || soc.getCommandType() == 5) {
			   if (command.size() == 1) {
				  out.addOutput(listInstantDamageSpellOp(Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_PLAYER, soc.getName(),
						UserCentral.getInstance().getOnlineUser(command.getSender())));
			   } else {
				  if (command.validate(1, InputType.KDLOC)) {
					 out.addOutput(listInstantDamageSpellOp(Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_KD, soc.getName()));
				  } else if (CommonMethods.getUserFromDB(command.get(1), false).equals("")) {
					 out.addOutput(listInstantDamageSpellOp(Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_PROV, soc.getName(),
						   command.getFullMessage()));
				  } else {
					 out.addOutput(listInstantDamageSpellOp(Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_PLAYER, soc.getName(),
						   CommonMethods.getUserFromDB(command.get(1), false)));
				  }
			   }
			}
		 } else {
			if (soc.getCommandType() == 4 || soc.getCommandType() == 5) {
			   if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
				  if (command.size() == 1 && removeInstantDamageSpellOp(Constants.IDSPELLOP_DELETE_SPELLOP, soc.getName())) {
					 out.addOutput("Lists cleared");
				  } else {
					 if (command.validate(1, InputType.KDLOC)
						   && removeInstantDamageSpellOp(Constants.IDSPELLOP_DELETE_SPELLOP_FOR_KD, soc.getName(), command.get(1))) {
						out.addOutput("Lists cleared");
					 }
					 if (command.size() == 3
						   && "not".equalsIgnoreCase(command.get(1))
						   && command.validate(2, InputType.KDLOC)
						   && removeInstantDamageSpellOp(Constants.IDSPELLOP_DELETE_SPELLOP_FOR_ALL_BUT_KD, soc.getName(),
								 command.get(2))) {
						out.addOutput("Lists cleared");
					 } else if (CommonMethods.getUserFromDB(command.get(1), false).equals("")
						   && removeInstantDamageSpellOp(Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_PROV, soc.getName(),
								 command.getFullMessage())) {
						out.addOutput("Lists cleared");
					 } else if (removeInstantDamageSpellOp(Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_PLAYER, soc.getName(),
						   CommonMethods.getUserFromDB(command.get(1), false))) {
						out.addOutput("Lists cleared");
					 }
				  }
			   }
			}
		 }
	  }
   }

   public void handleResetall(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 if (command.size() == 2 && command.validate(1, InputType.KDLOC)) {
			removeInstantDamageSpellOp(Constants.IDSPELLOP_DELETE_ALL_SPELLOPS_FOR_KD, command.get(1));
		 } else {
			removeInstantDamageSpellOp(Constants.IDSPELLOP_DELETE_ALL_SPELLOPS);
		 }
		 out.addOutput("Lists cleared");
	  }
   }

   public void handleSpells(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 String user = UserCentral.getInstance().getOnlineUser(command.getSender());
		 if (!"".equals(user)) {
			for (SpellOp so : SpellOp.getSpellOp(Constants.SPELLOP_GET_FOR_PLAYER, user)) {
			   out.addOutput(so.getIRCOutput(Constants.SPELLOP_GET_FOR_PLAYER));
			}
		 }
	  } else if (command.size() == 2) {
		 String user = CommonMethods.getUserFromDBFuzzyMatching(command.get(1));
		 if ("".equals(user)) {
			for (SpellOp so : SpellOp.getSpellOp(Constants.SPELLOP_GET_FOR_PROV, command.get(1).trim())) {
			   out.addOutput(so.getIRCOutput(Constants.SPELLOP_GET_FOR_PROV));
			}
		 } else {
			for (SpellOp so : SpellOp.getSpellOp(Constants.SPELLOP_GET_FOR_PLAYER, user)) {
			   out.addOutput(so.getIRCOutput(Constants.SPELLOP_GET_FOR_PLAYER));
			}
		 }
	  } else {
		 for (SpellOp so : SpellOp.getSpellOp(Constants.SPELLOP_GET_FOR_PROV, command.getFullMessage())) {
			out.addOutput(so.getIRCOutput(Constants.SPELLOP_GET_FOR_PROV));
		 }
	  }

   }

   public void handleTarget(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 String target = getSpellOpTarget(UserCentral.getInstance().getOnlineUser(command.getSender()));
		 if ("".equals(target)) {
			out.addOutput("No target registered");
		 } else {
			out.addOutput("Your current target is " + target);
		 }
	  } else if (command.size() > 1) {
		 String province = CommonMethods.provinceExists(command.getFullMessage());
		 if (!"".equals(province) && setSpellOpTarget(UserCentral.getInstance().getOnlineUser(command.getSender()), province)) {
			out.addOutput("Current target for posting ops/spells set to " + province);
		 } else {
			out.addOutput("No such province");
		 }
	  }
   }

   /**
    * Fetches and formats information about the commited instant damage spells/ops. How it's formatted depends on the parameters.
    * 
    * @param type
    * @param args
    * @return
    */
   private List<String> listInstantDamageSpellOp(final int type, final String... args) {
	  List<String> out = new LinkedList<String>();

	  HashMap<String, SpellOpPair> entries = new HashMap<String, SpellOpPair>();
	  int totalresults = 0;
	  int totaltimes = 0;
	  for (InstantDamageSpellOp idso : InstantDamageSpellOp.getInstantDamageSpellOp(type, args)) {
		 totalresults += idso.getResults();
		 totaltimes += idso.getTimes();
		 String who = "";
		 if (type == Constants.IDSPELLOP_GET_FOR_SPELLOP || type == Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_KD
			   || type == Constants.IDSPELLOP_GET_FOR_SPELLOP_FOR_PLAYER) {
			who = idso.getProvname();
		 } else {
			who = idso.getSender();
		 }
		 if (entries.containsKey(who)) {
			SpellOpPair temp = entries.get(who);
			temp.result += idso.getResults();
			temp.times += idso.getTimes();
			entries.put(who, temp);
		 } else {
			entries.put(who, new SpellOpPair(idso.getResults(), idso.getTimes()));
		 }
	  }
	  out.add(args[0] + " results" + (args.length == 2 ? " for " + args[1] : "") + ":");
	  String output = "";
	  for (Entry<String, SpellOpPair> e : entries.entrySet()) {
		 if (output.length() > 300) {
			out.add(output);
			output = "";
		 }
		 output += Colors.BLUE + e.getKey() + " " + Colors.RED + "(" + e.getValue().result + " in " + e.getValue().times
			   + " tries)" + Colors.NORMAL + " | ";
	  }
	  if (output.length() > 0) {
		 out.add(output);
	  }
	  out.add("Totals: " + Colors.DARK_GREEN + totalresults + " in " + totaltimes + " tries");
	  return out;
   }

   /**
    * Fetches and formats information about the specified spell/op. Either lists only the active spell/ops of the specified type,
    * or includes who doesn't have it as well (if kd is specified)
    * 
    * @param type
    * @param args
    * @return
    */
   private List<String> listSpellOp(final int type, final String... args) {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 String output = "";
		 List<String> hasSpellOp = new LinkedList<String>();
		 out.add(Colors.DARK_GREEN + "Active " + args[0] + ":");
		 for (SpellOp so : SpellOp.getSpellOp(type, args)) {
			Long diff = so.getEndtime() - GregorianCalendar.getInstance().getTimeInMillis();
			Double hour = diff / 3600000d;
			NumberFormat nf = NumberFormat.getNumberInstance();
			nf.setGroupingUsed(false);
			nf.setMaximumFractionDigits(1);
			nf.setMinimumFractionDigits(0);

			output += Colors.OLIVE + so.getProvname() + " " + so.getKd() + Colors.BLUE + " (" + nf.format(hour) + "hrs)"
				  + Colors.NORMAL + " | ";

			if (output.length() > 150) {
			   out.add(output.substring(0, output.length() - 2));
			   output = "";
			}
			hasSpellOp.add(so.getProvname());
		 }
		 if (output.length() > 3) {
			out.add(output.substring(0, output.length() - 2));
		 }

		 if (args.length == 2) {
			ps = DBConnectionFactory
				  .getConnectionHandler()
				  .getConnection()
				  .prepareStatement(
						"SELECT province.name,kd.loc FROM " + "province,kd WHERE kd.loc = province.kdloc AND kd.loc = ? ");
			ps.setString(1, args[1]);
			rs = ps.executeQuery();

			output = "";
			out.add(Colors.DARK_GREEN + "Provs with no " + args[0] + ":");
			while (rs.next()) {
			   if (!hasSpellOp.contains(rs.getString("province.name"))) {
				  output += Colors.OLIVE + rs.getString("province.name") + rs.getString("kd.loc") + Colors.NORMAL + " | ";

				  if (output.length() > 150) {
					 out.add(output.substring(0, output.length() - 2));
					 output = "";
				  }
			   }
			}
			if (output.length() > 3) {
			   out.add(output.substring(0, output.length() - 2));
			}
		 }
	  } catch (SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } catch (IndexOutOfBoundsException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (Exception e) {
		 }
		 try {
			ps.close();
		 } catch (Exception e) {
		 }
	  }
	  return out;
   }
}
